#include "iConfig.hpp"

#include <glf/app.h>

#include <glf/debug/debugDisplay.h>

#include <glf/core/timer.h>
#include <glf/core/event.h>
#include <glf/io/fileStream.h>

#if GLF_AS_DLL
T_FUNC_NEWAPP NewApp = NULL;
T_FUNC_GLFALLOC GlfAllocFunc = NULL;
T_FUNC_GLFALLOC2 GlfAllocFunc2 = NULL;
T_FUNC_GLFFREE GlfFreeFunc = NULL;
extern "C"
{
	void SetNewApp(T_FUNC_NEWAPP pNewApp) { NewApp = pNewApp; };
	void SetGlfAlloc(T_FUNC_GLFALLOC pGlfAlloc) { GlfAllocFunc = pGlfAlloc; };
	void SetGlfAlloc2(T_FUNC_GLFALLOC2 pGlfAlloc) { GlfAllocFunc2 = pGlfAlloc; };
	void SetGlfFree(T_FUNC_GLFFREE pGlfFree) { GlfFreeFunc = pGlfFree; };
};
#endif

#include <glf/remote/controller.h>

#include <glf/debugger/debugger.h>
#include <glf/debugger/perfCounters.h>
#include <glf/core/infos.h>

#include <sstream>

namespace glf {

#if GLF_AS_DLL
void App::SetInstance(App* pApp)
{
	mInstance = pApp;
}
#endif //GLF_AS_DLL

//cdonohue's simple hash, move me somewhere
unsigned int hashstring(const char *str)
{
    unsigned int hash = 5381;
    int c;
	while ((c = *str++)) { hash = ((hash << 5) + hash) + c; }
    return hash;
}

const char* gPropertySessionStart			= "session.start";
const char* gPropertySessionRunningDuration	= "session.runningDuration";
const char* gPropertySessionTimeInBackground= "session.timeInBackground";
const char* gPropertySessionResumeCount		= "session.resumeCount";

static int64 DELAY_FOR_IDLE = 20 * 1000; //20000ms = 20sec
static int64 DELAY_FOR_IDLE_RESET = 60 * 1000; //60sec

struct AppEventReceiver : public EventReceiver {
	int64 mStartTime;
	int64 mStartBackground;
	int64 mStartBackgroundTicks;
	
	int64 mTimeSinceLastInput;

	

	AppEventReceiver(App& app)
		:	mApp(app)
		,	mStartTime(GetMilliseconds())
		,	mStartBackground(mStartTime)
		,	mTimeSinceLastInput(mStartTime)
	{
		PropertyMap& pm = PropertyMap::Instance();

		pm.SetPropertyT<int64>(gPropertySessionStart, mStartTime, PropertyMap::EF_PERSISTANT|PropertyMap::EF_LR_QUERY_SUSPENDRESUME);
		pm.SetPropertyT<int64>(gPropertySessionRunningDuration, 0, PropertyMap::EF_PERSISTANT|PropertyMap::EF_LR_QUERY_SUSPENDRESUME);
		pm.SetPropertyT<int64>(gPropertySessionTimeInBackground, 0, PropertyMap::EF_PERSISTANT|PropertyMap::EF_LR_QUERY_SUSPENDRESUME);
		pm.SetPropertyT<int>(gPropertySessionResumeCount, 0, PropertyMap::EF_PERSISTANT|PropertyMap::EF_LR_QUERY_SUSPENDRESUME);

		mApp.GetEventMgr().AddEventReceiver(this, 1000);
	}

	~AppEventReceiver()
	{
		mApp.GetEventMgr().RemoveEventReceiver(this);
	}

	static void Increment(const char* prop, int64 inc=1)
	{
		PropertyMap& pm = PropertyMap::Instance();

		int64 value = pm.GetPropertyInt64(prop);
		pm.SetPropertyT(prop, value + inc);
	}
	
	static void Increment(const char* prop, int inc=1)
	{
		PropertyMap& pm = PropertyMap::Instance();

		int value = pm.GetPropertyInt32(prop);
		pm.SetPropertyT(prop, value + inc);
	}

	void Update()
	{
		int64 nowTime = GetMilliseconds();

		PropertyMap& pm = PropertyMap::Instance();

		pm.SetPropertyT(gPropertySessionRunningDuration, nowTime - mStartTime);

		if(GetIdleTime() > DELAY_FOR_IDLE_RESET) {
			ResetIdleTime();
		}
	}

	bool OnEvent(const CoreEvent &evt) 
	{
		if(evt.mType == AET_SYSTEM)
		{
			const SystemEvent& sysEvt = static_cast<const SystemEvent&>(evt);
		
			PropertyMap& pm = PropertyMap::Instance();

			if(sysEvt.mSubType == glf::SET_BECAME_ACTIVE)
			{
				pm.SetPropertyT<bool>("state.foreground", true);

				int64 elapsedTicks = GetTicks() - mStartBackgroundTicks;
				
				OffsetTimer(elapsedTicks);

				int64 timeSentInBackground = GetMilliseconds() - mStartBackground;

				Increment(gPropertySessionTimeInBackground, timeSentInBackground);

				Increment(gPropertySessionResumeCount, 1);
			}
			else if(sysEvt.mSubType == glf::SET_WILL_RESIGN_ACTIVE)
			{
				pm.SetPropertyT<bool>("state.foreground", false);

				ResetIdleTime();

				mStartBackground = GetMilliseconds();
				mStartBackgroundTicks = GetTicks();
			}
		}
		else if(evt.mType == IET_TOUCHPAD_MOVED)
		{
			ResetIdleTime();
		}
		return false;
	}

	int64 GetIdleTime() const {
		return GetMilliseconds() - mTimeSinceLastInput;
	}

	void ResetIdleTime() {
		int64 nowTime = GetMilliseconds();

		int64 idle = nowTime - mTimeSinceLastInput;

		if(idle > DELAY_FOR_IDLE) {
			PropertyMap& pm = PropertyMap::Instance();
			int64 newIdle = pm.GetPropertyInt64("session.idle", int64(0)) + idle;
			pm.SetPropertyT<int64>("session.idle", newIdle, PropertyMap::EF_PERSISTANT);
		}

		mTimeSinceLastInput = nowTime;
	}
private:
	App& mApp;
};


struct ImplAppBase {
	App& mApp;
	AppEventReceiver mEventReceiver;

	Macro* mAutoStart;
	
	ImplAppBase(App& app) 
		:	mApp(app)
		,	mEventReceiver(app)
		,	mAutoStart(0)
	{
	}

	virtual ~ImplAppBase() {
		delete mAutoStart;
	}

	static void EndOfAutoStartMacro(Macro* macro, Macro::EventType et, void* param, void* ud) {
		ImplAppBase* impl = static_cast<ImplAppBase*>(ud);
		
		if(macro == impl->mAutoStart && et == Macro::EET_PLAYBACKENDED) {
			//delete impl->mAutoStart;
			//impl->mAutoStart = 0;
		}
	}

	virtual const char* GetDeviceName() const {
		return "null";
	}

	void *GetFrameBufferObject(int windowIndex)
	{
		return (void *)(windowIndex == 0 ? 0 : -1);
	}

	bool HasFrameBufferObject(int windowIndex) const
	{
		return windowIndex == 0;
	}

	bool Prepare(int windowIndex) {
		glf::MarkUsed(windowIndex);
		return windowIndex == 0;
	}

	virtual void Present(int windowIndex) {
		glf::MarkUsed(windowIndex);
	}

	void GetWindowSize(int& width, int& height, int windowIndex) const {
		width = 0;
		height = 0;
	}

	bool ConnectDisplay(int windowIndex)
	{
		return false;
	}

	bool DisconnectDisplay(int windowIndex)
	{
		return false;
	}

	DisplayStatus GetDisplayStatus(int windowIndex)
	{
		return windowIndex == 0 ? DS_CONNECTED : DS_UNAVAILABLE;
	}
	virtual void StartRemoteController(const std::string& ip, int port) {

	}
};

}

#if GLF_PLATFORM_IPHONE
	#include "app_iphone_.hpp"
#elif GLF_PLATFORM_MACOSX
	#include "app_macosx_.hpp"
#else
	#include GLFi_HEADER_API(app, GLF_API_APP)
#endif


namespace glf {

//en, fr, de, it, sp, jp, count
const LangInfo gLangInfos[] = {
	{ "english", "en" },
	{ "french", "fr" },
	{ "german", "ge" },
	{ "italian", "it" },
	{ "spanish", "sp" },
	{ "dutch", "du" },
	{ "japanese", "jp" },
};

App *App::mInstance = 0;


App::App(CreationFlag cf) 
	:	mOrientation(ORIENTATION_UNDEFINED)
	,	mSupportedOrientations(ORIENTATION_ALL)
	,	mReservedExtraContexts(0)
	,	mFs(GetGlobals()->fileSystem)
	,	mInputManager(GetGlobals()->inputMgr)
	,	mEventManager(GetGlobals()->eventManager)
	,	mRemoteController(0)
	
	,	m(GLF_NEW_IMPL(*this, cf))
{

	GLF_CHECK_IMPL_SIZE();

	GetGlobals()->app = this;
	mInstance = this;

	mEventManager.AddEventReceiver(this);
	mEventManager.AddEventReceiver(&mInputManager);
}

App::~App() {
	mEventManager.RemoveEventReceiver(&mInputManager);
	mEventManager.RemoveEventReceiver(this);
	Deinit();
	delete mRemoteController;
}

#if GLF_VIRTUAL_FILESTREAM
void App::FileOutAllFiles() {
	GetGlobals()->fileStreamMgr.FileOutAllFiles();
}
#endif

bool App::MyInit() {

	PropertyMap::Instance().LoadLastPersistant();

	Memset(mPlatformInfos.mExtraContext, 0, sizeof(mPlatformInfos.mExtraContext));
	GetGlobals()->threadMgr.Init();
	if(Init(mCreationSettings)) {
		// make a run for the main thread
		Thread::OnStart();
		return true;
	}
	return false;
}

bool App::Init(CreationSettings& cs) {
	// If you hit this assert, you need to increase the value of GLF_MAX_EXTRA_CONTEXT
	// in your glf_config.h
	GLF_ASSERT(cs.extraContexts <= GLF_MAX_EXTRA_CONTEXT);

	//Use this creation setting value by default for all implementations.
	mAppSettings.screenScale = cs.screenScale;

	bool isInitOk = m.Init(cs);

	if(isInitOk) {
		PropertyMap& pm = PropertyMap::Instance();

		std::string argv;
		for(int i = 0; i < GetGlobals()->argc; ++i) {
			argv += GetGlobals()->argv[i];
			argv += ";";
		}
		
		pm.SetProperty("session.argv", argv, PropertyMap::EF_PERSISTANT|PropertyMap::EF_LR_QUERY_FULL);

		{
			int launchCount = pm.GetPropertyInt32("session.launchCount", 0);
			std::string buildUuid = pm.GetPropertyString("build.uuid", "");
			std::string lastBuildUuid = pm.GetPropertyString("build.lastUuid", "");
			
			std::string macAddress = pm.GetPropertyString("device.wifiMacAddress", "00:00:00:00:00:00");
			
			{
				std::stringstream ss;
				
				ss << buildUuid << macAddress << ":" << launchCount;
				std::string sessionUid = ss.str();
				int hash = hashstring(sessionUid.c_str());
				char buffer[64];
				sprintf(buffer, "%08x", hash);

				pm.SetProperty("session.launchUid", buffer, PropertyMap::EF_LR_ALWAYS);
			}
			
			{
				int lastLaunchCount = launchCount - 1;

				std::stringstream ss;
				ss << lastBuildUuid << macAddress << ":" << lastLaunchCount; // << ":" << GetTicks();
				std::string sessionUid = ss.str();
				int hash = hashstring(sessionUid.c_str());
				char buffer[64];
				sprintf(buffer, "%08x", hash);

				pm.SetProperty("session.lastLaunchUid", buffer, PropertyMap::EF_PERSISTANT|PropertyMap::EF_LR_ALWAYS);
			}
			{

			}
		}
		
		if (cs.supportedOrientations != ORIENTATION_UNDEFINED)
			SetSupportedOrientations(cs.supportedOrientations);

		{
			char** argv = GetGlobals()->argv;
			int argc = GetGlobals()->argc;
			
			std::string curArg;
			for(int i = 1; i < argc; ++i) {
				std::string arg = argv[i];

				if(!arg.empty() && arg[0] == '-') {
					curArg = arg.substr(1);
				} else {
					if(Stricmp(curArg.c_str(), "startHomeMacro") == 0) {
						m.mAutoStart = new Macro(GetAppEventMgr(), arg, true, ios::loc_home);
					} else if(Stricmp(curArg.c_str(), "startDataMacro") == 0) {
						m.mAutoStart = new Macro(GetAppEventMgr(), arg, true, ios::loc_data);
					}
				}
			}
			
			if(m.mAutoStart) {
				m.mAutoStart->StartPlaying();
				m.mAutoStart->AddCallback(ImplAppBase::EndOfAutoStartMacro, &m);
			}
		}
	}

	return isInitOk;
}

void App::Deinit() {
	m.Deinit();
	GetGlobals()->threadMgr.DeInit();
}

void App::SetCreationSettings(CreationSettings& cs)
{
	mCreationSettings = cs;
}

void App::SetPlatformInfos(PlatformInfo& info)
{
	mPlatformInfos = info;
}

void App::SetAsMainThread() {
	m.SetCurrentContext(m.GetMainContext());
	GetGlobals()->threadMgr.mMainThreadId = GetCurrentThreadId();
}

void App::UnsetAsMainThread() {
	m.SetCurrentContext(NULL);
	GetGlobals()->threadMgr.mMainThreadId = 0;
}

void App::Run() {

	GLF_ASSERT(IsRunnable());

	mIsRunning = true;

	// WP8-NOTE: Windows 8 will call update on a timer, much like iOS.
#if GLF_PLATFORM_WP8 == 0
	while(mIsRunning) {
		Update();
	}
#endif
}

bool App::IsRunning() const
{
	return mIsRunning;
}

void App::Stop() {
	mIsRunning = false;
}

void App::SetTargetFps(float fps) {
	mTargetFps = fps;
}

float App::GetTargetFps() const {
	return mTargetFps;
}

void App::Update() {
	m.mEventReceiver.Update();
	m.Update();

	if(m.mAutoStart) {
		if(m.mAutoStart->GetProgress() >= m.mAutoStart->GetDuration()) {
			delete m.mAutoStart;
			m.mAutoStart = 0;
		}
	}

	glf::DebugDisplay::Get().reset();

	if(mRemoteController)
		mRemoteController->Update();

#if GLF_ENABLE_DEBUGGER && !defined(GLF_PLATFORM_WP8)
	{
		static int64 sLastTime = GetMilliseconds();
		static int64 sLastFrameTime = GetMilliseconds();
		static RuntimeInfos sInfos;
		static int threadCount = 0;
		static int glfThreadCount = 0;

		int64 time = GetMilliseconds();
		
		if(time - sLastTime > 1000)
		{
			Thread::GetNumberOfThreads(threadCount, glfThreadCount);
			GetRuntimeInfos(sInfos);
			sLastTime = time;
		}

		glf::debugger::PerfCounters::UpdateValue("frame.timestep", (int)(time - sLastFrameTime));

		glf::debugger::PerfCounters::UpdateValue("threads.all", threadCount);
		glf::debugger::PerfCounters::UpdateValue("threads.glf", glfThreadCount);

		glf::debugger::PerfCounters::UpdateValue("runtime.residentSize", uint(sInfos.taskResidentSize / 1024));
		glf::debugger::PerfCounters::UpdateValue("runtime.virtualSize", uint(sInfos.taskVirtualSize / 1024));
		glf::debugger::PerfCounters::UpdateValue("runtime.freeMemory", uint(sInfos.freeMemory / 1024));
		glf::debugger::PerfCounters::UpdateValue("runtime.usedMemory", uint(sInfos.usedMemory / 1024));

		sLastFrameTime = time;
	}
#endif

	mInputManager.UpdateFrame();

	FileStream::DispatchDoneReads();

	GetAppEventMgr().DispatchEvents();
}

extern bool gEnableProfilerScopes;

void App::EnableProfilerEvents(bool isEnabled) {
	gEnableProfilerScopes = isEnabled;
}

bool App::IsProfilerEventsEnabled() const {
	return gEnableProfilerScopes;
}

void App::BeginProfilerEvent(const char* name) {
}

void App::EndProfilerEvent() {
}

void *App::GetFrameBufferObject(int windowIndex)
{
	return m.GetFrameBufferObject(windowIndex);
}

bool App::HasFrameBufferObject(int windowIndex) const
{
	return m.HasFrameBufferObject(windowIndex);
}

bool App::Prepare(int windowIndex) {
	return m.Prepare(windowIndex);
}

void App::Present(int windowIndex) {
	m.Present(windowIndex);
}

void App::Destroy() {
	delete this;
}

PlatformInfo& App::GetPlatformInfos()  {
	return mPlatformInfos;
}

const AppSettings& App::GetAppSettings() const {
	return mAppSettings;
}


const CreationSettings& App::GetCreationSettings() const {
	return mCreationSettings;
}

bool App::HasContext() const {
	return m.GetCurrentContext() != NULL;
}

bool App::ReserveContext() {
	bool success = false;
	SpinLockScope lock(mExtraContextLock);
	if(!HasContext() && mReservedExtraContexts < mCreationSettings.extraContexts) {
		success = m.SetCurrentContext(mPlatformInfos.mExtraContext[mReservedExtraContexts]);
		if(success) {
			++mReservedExtraContexts;
		}
	}
	return success;
}

bool App::ReleaseContext() {
	bool success = false;
	SpinLockScope lock(mExtraContextLock);
	void* ctx = m.GetCurrentContext();
	if(ctx && ctx != m.GetMainContext()) {
		success = m.SetCurrentContext(NULL);
		if(success) {
			for(int i = 0; i < mReservedExtraContexts; ++i) {
				if(mPlatformInfos.mExtraContext[i] == ctx) {
					--mReservedExtraContexts;
					mPlatformInfos.mExtraContext[i] = mPlatformInfos.mExtraContext[mReservedExtraContexts];
					mPlatformInfos.mExtraContext[mReservedExtraContexts] = ctx;
					break;
				}
			}
		}
	}
	return success;
}

App* App::GetInstance() {
	return mInstance;
}

EventManager& App::GetEventMgr() {
	return mEventManager;
}

InputManager& App::GetInputMgr() {
	return mInputManager;
}

Fs& App::GetFs() {
	return mFs;
}

bool App::QueryInfo(const char* key, char* result, size_t& resultLength) {
	return false;
}

Orientation App::GetOrientation() const {
	return mOrientation;
}

bool App::SetOrientation(Orientation newOrientation) {
	if (IsOrientationSupported(newOrientation)) {
		mOrientation = newOrientation;
		return true;
	}
	return false;
}

bool App::IsOrientationSupported(Orientation orientation) const
{
	return (mSupportedOrientations & (int)orientation) != 0;
}

void App::SetSupportedOrientations(int supportedOrientations)
{
	mSupportedOrientations = supportedOrientations;
}

const char* App::GetDeviceName() const {
	return m.GetDeviceName();
}

void App::GetWindowSize(int& width, int& height, int windowIndex) const {
	m.GetWindowSize(width, height, windowIndex);
}

Point<short> App::ConvertPosDeviceToScreen(const Point<float> &devicePos) const
{
	Orientation ori = GetOrientation();
	const float multiplier = GetAppSettings().screenScale;
	Point<short> pos(
		static_cast<short>(devicePos.x * multiplier),
		static_cast<short>(devicePos.y * multiplier));
	int width, height;
	GetWindowSize(width, height);
	switch (ori)
	{
	case ORIENTATION_PORTRAIT:
		break;
	case ORIENTATION_PORTRAIT_REVERSED:
		pos.x = width - pos.x;
		pos.y = height - pos.y;
		break;
	case ORIENTATION_LANDSCAPE_RIGHT:
		{
			const int tmpX = pos.x;
			pos.x = pos.y;
			pos.y = width - tmpX;
		}
		break;
	case ORIENTATION_LANDSCAPE_LEFT:
		{
			const int tmpX = pos.x;
			pos.x = height - pos.y;
			pos.y = tmpX;
		}
		break;
	case ORIENTATION_ALL:
	case ORIENTATION_UNDEFINED:
	default:
		break;
	}
	return pos;
}

Point<float> App::ConvertPosScreenToDevice(const Point<short> &screenPos) const
{
	Orientation ori = GetOrientation();
	const float multiplier = 1.0f / GetAppSettings().screenScale;
	Point<float> pos(
		static_cast<short>(screenPos.x * multiplier),
		static_cast<short>(screenPos.y * multiplier));
	int width, height;
	GetWindowSize(width, height);
	width = (int)(width * multiplier);
	height = (int)(height * multiplier);
	switch (ori)
	{
	case ORIENTATION_PORTRAIT:
		break;
	case ORIENTATION_PORTRAIT_REVERSED:
		pos.x = width - pos.x;
		pos.y = height - pos.y;
		break;
	case ORIENTATION_LANDSCAPE_RIGHT:
		{
			const float tmpX = pos.x;
			pos.x = width - pos.y;
			pos.y = tmpX;
		}
		break;
	case ORIENTATION_LANDSCAPE_LEFT:
		{
			const float tmpX = pos.x;
			pos.x = pos.y;
			pos.y = height - tmpX;
		}
		break;
	case ORIENTATION_ALL:
	case ORIENTATION_UNDEFINED:
	default:
		break;
	}
	return pos;
}

Point<float> App::ConvertPosScreenToNormalizedDevice(const Point<short> &screenPos) const
{
	Point<float> normalizedDevicePos = ConvertPosScreenToDevice(screenPos);
	const float multiplier = 1.0f / GetAppSettings().screenScale;
	int width, height;
	GetWindowSize(width, height);
	width = (int)(width * multiplier);
	height = (int)(height * multiplier);
	normalizedDevicePos.x = normalizedDevicePos.x / width;
	normalizedDevicePos.y = normalizedDevicePos.y / height;
	return normalizedDevicePos;
}

Point<short> App::ConvertPosNormalizedDeviceToScreen(const Point<float> &normalizedDevicePos) const
{
	Point<float> devicePos;
	int width, height;
	GetWindowSize(width, height);
	devicePos.x = normalizedDevicePos.x * width;
	devicePos.y = normalizedDevicePos.y * height;
	return ConvertPosDeviceToScreen(devicePos);
}

void App::GetScreenSize(int& width, int& height, int windowIndex) const
{
	GetWindowSize(width, height, windowIndex);
	if (windowIndex == 0)
	{
		Orientation ori = GetOrientation();
		if (ori == ORIENTATION_LANDSCAPE_RIGHT || ori == ORIENTATION_LANDSCAPE_LEFT)
		{
			//swap
			const int memWidth = width;
			width = height;
			height = memWidth;
		}
	}
}

Point<float> App::ConvertPosScreenToNormalizedScreen(const Point<short> &screenPos) const
{
	int width, height;
	GetScreenSize(width, height);
	if (width && height)
		return Point<float>(
			(float)screenPos.x / width,
			(float)screenPos.y / height);
	return Point<float>(0.0f, 0.0f);
}

Point<short> App::ConvertPosNormalizedScreenToScreen(const Point<float> &normalizedScreen) const
{
	int width, height;
	GetScreenSize(width, height);
	return Point<short>(
		(short)(normalizedScreen.x * width),
		(short)(normalizedScreen.y * height));
}

void App::SetRemoteController(remote::Controller *remoteController)
{
	if (mRemoteController)
		delete mRemoteController;
	mRemoteController = remoteController;
}

bool App::ConnectDisplay(int windowIndex)
{
	return m.ConnectDisplay(windowIndex);
}

bool App::DisconnectDisplay(int windowIndex)
{
	return m.DisconnectDisplay(windowIndex);
}

DisplayStatus App::GetDisplayStatus(int windowIndex)
{
	return m.GetDisplayStatus(windowIndex);
}

void App::StartRemoteController(const std::string& ip, int port) {
	m.StartRemoteController(ip, port);
}

void Init() {
	init();
}

} //namespace glf

